use core::hint::spin_loop;

use crate::{
    num::SyscallIndex,
    scheme::{FileInfo, SchemeTy},
};

#[naked]
pub extern "C" fn syscall(
    _id: u64,
    _arg1: usize,
    _arg2: usize,
    _arg3: usize,
    _arg4: usize,
    _arg5: usize,
) -> usize {
    unsafe {
        core::arch::naked_asm!(
            "mov rax, rdi",
            "mov rdi, rsi",
            "mov rsi, rdx",
            "mov rdx, rcx",
            "mov r10, r8",
            "mov r8, r9",
            "syscall",
            "ret",
        )
    }
}

pub fn exit(code: usize) -> ! {
    syscall(SyscallIndex::Exit as u64, code, 0, 0, 0, 0);

    loop {
        spin_loop();
    }
}

pub fn wait(pid: usize) -> usize {
    loop {
        let is_exited = syscall(SyscallIndex::IsExited as u64, pid, 0, 0, 0, 0);
        if is_exited != 0 {
            break;
        }
        spin_loop();
    }

    0
}

use core::mem::{transmute, variant_count};

use alloc::{string::String, vec::Vec};

#[repr(C)]
pub enum OpenMode {
    Read = 0,
    Write = 1,
    ReadWrite = 2,
}

impl From<usize> for OpenMode {
    fn from(number: usize) -> Self {
        let length = variant_count::<Self>();
        if number >= length {
            panic!("Invalid openmode index: {}", number);
        }
        unsafe { transmute(number as u32) }
    }
}

pub fn open(path: String, mode: OpenMode) -> usize {
    syscall(
        SyscallIndex::Open as u64,
        path.as_ptr() as usize,
        path.len(),
        mode as usize,
        0,
        0,
    )
}

pub fn close(fd: usize) -> usize {
    syscall(SyscallIndex::Close as u64, fd, 0, 0, 0, 0)
}

pub fn read(fd: usize, buf: &mut [u8]) -> usize {
    syscall(
        SyscallIndex::Read as u64,
        fd,
        buf.as_mut_ptr() as usize,
        buf.len(),
        0,
        0,
    )
}

pub fn write(fd: usize, buf: &[u8]) -> usize {
    syscall(
        SyscallIndex::Write as u64,
        fd,
        buf.as_ptr() as usize,
        buf.len(),
        0,
        0,
    )
}

pub fn fsize(fd: usize) -> usize {
    syscall(SyscallIndex::Fsize as u64, fd, 0, 0, 0, 0)
}

pub fn ftype(fd: usize) -> SchemeTy {
    let ty = syscall(SyscallIndex::Ftype as u64, fd, 0, 0, 0, 0);
    if ty == usize::MAX {
        return SchemeTy::File;
    }
    SchemeTy::from(ty)
}

pub fn list_dir(path: String) -> Vec<FileInfo> {
    fn dir_item_num(path: String) -> usize {
        syscall(
            SyscallIndex::DirItemNum as u64,
            path.as_ptr() as usize,
            path.len(),
            0,
            0,
            0,
        )
    }

    #[derive(Default, Clone)]
    struct TemporyInfo {
        name: &'static [u8],
        ty: SchemeTy,
    }

    let len = dir_item_num(path.clone());
    let buf = alloc::vec![TemporyInfo::default(); len];

    syscall(
        SyscallIndex::ListDir as u64,
        path.as_ptr() as usize,
        path.len(),
        buf.as_ptr() as usize,
        0,
        0,
    );

    let mut infos = Vec::new();
    for info in buf.iter() {
        infos.push(FileInfo {
            name: String::from_utf8(info.name.to_vec()).unwrap(),
            ty: info.ty,
        })
    }
    infos
}

pub fn ioctl(fd: usize, cmd: usize, arg: usize) -> usize {
    syscall(SyscallIndex::IoCtl as u64, fd, cmd, arg, 0, 0)
}

pub fn lseek(fd: usize, ptr: usize) -> usize {
    syscall(SyscallIndex::LSeek as u64, fd, ptr, 0, 0, 0)
}

pub fn mmap(vaddr: usize, paddr: usize, len: usize, flags: usize) -> usize {
    syscall(SyscallIndex::Mmap as u64, vaddr, paddr, len, flags, 0)
}

pub fn alloc_for_dma(size: usize) -> usize {
    syscall(SyscallIndex::AllocForDma as u64, size, 0, 0, 0, 0)
}

pub fn dealloc_for_dma(addr: usize, size: usize) -> usize {
    syscall(SyscallIndex::DeallocForDma as u64, addr, size, 0, 0, 0)
}

pub fn getpid() -> usize {
    syscall(SyscallIndex::GetPid as u64, 0, 0, 0, 0, 0)
}

pub fn set_init_pid(pid: usize) {
    syscall(SyscallIndex::SetInitPid as u64, pid, 0, 0, 0, 0);
}

pub fn execve(buf: &[u8], argc: usize, argv: usize) -> usize {
    syscall(
        SyscallIndex::Execve as u64,
        buf.as_ptr() as usize,
        buf.len(),
        argc,
        argv,
        0,
    )
}

pub fn change_cwd(path: String) {
    syscall(
        SyscallIndex::ChangeCwd as u64,
        path.as_ptr() as usize,
        path.len(),
        0,
        0,
        0,
    );
}

pub fn get_cwd() -> String {
    let ptr = syscall(SyscallIndex::GetCwd as u64, 0, 0, 0, 0, 0);
    let path_buf_ptr = unsafe { (ptr as *const u64).read() };
    let path_buf_len = unsafe { (ptr as *const usize).add(1).read() };
    let path_buf = unsafe { core::slice::from_raw_parts(path_buf_ptr as *const u8, path_buf_len) };
    String::from_utf8(path_buf.to_vec()).unwrap()
}
